# Copyright (c) 2023, Vladimir Botka <vbotka@gmail.com>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later

from __future__ import annotations


DOCUMENTATION = """
name: fqdn_valid
short_description: Validates fully-qualified domain names against RFC 1123
version_added: 8.1.0
author: Vladimir Botka (@vbotka)
requirements:
  - fqdn>=1.5.1 (PyPI)
description:
  - This test validates Fully Qualified Domain Names (FQDNs)
    conforming to the Internet Engineering Task Force specification
    RFC 1123 and RFC 952.
  - The design intent is to validate that a string would be
    traditionally acceptable as a public Internet hostname to
    RFC-conforming software, which is a strict subset of the logic
    in modern web browsers like Mozilla Firefox and Chromium that
    determines whether make a DNS lookup.
  - Certificate Authorities like Let's Encrypt run a narrower set of
    string validation logic to determine validity for issuance. This
    test is not intended to achieve functional parity with CA
    issuance.
  - Single label names are allowed by default (O(min_labels=1)).
options:
  _input:
    description: Name of the host.
    type: str
    required: true
  min_labels:
    description: Required minimum of labels, separated by period.
    default: 1
    type: int
    required: false
  allow_underscores:
    description: Allow underscore characters.
    default: false
    type: bool
    required: false
"""

EXAMPLES = """
- name: Make sure that hostname is valid
  ansible.builtin.assert:
    that: hostname is community.general.fqdn_valid

- name: Make sure that hostname is at least 3 labels long (a.b.c)
  ansible.builtin.assert:
    that: hostname is community.general.fqdn_valid(min_labels=3)

- name: Make sure that hostname is at least 2 labels long (a.b). Allow '_'
  ansible.builtin.assert:
    that: hostname is community.general.fqdn_valid(min_labels=2, allow_underscores=True)
"""

RETURN = """
_value:
  description: Whether the name is valid.
  type: bool
"""

import typing as t
from collections.abc import Callable

from ansible.errors import AnsibleFilterError

ANOTHER_LIBRARY_IMPORT_ERROR: ImportError | None
try:
    from fqdn import FQDN
except ImportError as imp_exc:
    ANOTHER_LIBRARY_IMPORT_ERROR = imp_exc
else:
    ANOTHER_LIBRARY_IMPORT_ERROR = None


def fqdn_valid(name: t.Any, min_labels: t.Any = 1, allow_underscores: t.Any = False) -> bool:
    """
    Example:
      - 'srv.example.com' is community.general.fqdn_valid
      - 'foo_bar.example.com' is community.general.fqdn_valid(allow_underscores=True)
    """

    if ANOTHER_LIBRARY_IMPORT_ERROR:
        raise AnsibleFilterError(
            "Python package fqdn must be installed to use this test."
        ) from ANOTHER_LIBRARY_IMPORT_ERROR

    if not isinstance(name, str):
        raise AnsibleFilterError(f"The name parameter must be a string, got {name!r} of type {type(name)}")

    if not isinstance(min_labels, int):
        raise AnsibleFilterError(
            f"The min_labels parameter must be an integer, got {min_labels!r} of type {type(min_labels)}"
        )

    if not isinstance(allow_underscores, bool):
        raise AnsibleFilterError(
            f"The allow_underscores parameter must be a boolean, got {allow_underscores!r} of type {type(allow_underscores)}"
        )

    fobj = FQDN(name, min_labels=min_labels, allow_underscores=allow_underscores)
    return fobj.is_valid


class TestModule:
    """Ansible test hostname validity.
    https://pypi.org/project/fqdn/
    """

    def tests(self) -> dict[str, Callable]:
        return {
            "fqdn_valid": fqdn_valid,
        }
